﻿using System;
using System.Text;
using SSG.Tools;

namespace SSG.Model
{
    /// <summary>
    /// Altitude / Elevation
    /// </summary>
    public class Altitude : ICloneable
    {
        public static Altitude Altitude0 = new Altitude(0, AltitudeMode.Agl, AltitudeModifier.Exact);

        /// <summary>
        /// Relative to surface? 
        /// </summary>
        public enum AltitudeMode
        {
            Agl,
            Msl
        }

        /// <summary>
        /// Modifier
        /// </summary>
        public enum AltitudeModifier
        {
            Exact,
            AtOrBelow,
            AtOrAbove,
            Range
        }

        /// <summary>
        /// Calculated?
        /// </summary>
        public bool IsCalculated { get; set; }

        private double _valueFt = 0;
        private double _valueM = 0;
        private double _boundaryFt = 0;
        private double _boundaryM = 0;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="valueFt"></param>
        /// <param name="mode"></param>
        /// <param name="modifier"></param>
        /// <param name="calculated"></param>
        public Altitude(double valueFt, AltitudeMode mode, AltitudeModifier modifier, bool calculated = false)
        {
            this.ValueFt = valueFt;
            this.BoundaryFt = valueFt;
            this.Mode = mode;
            this.Modifier = modifier;
            this.IsCalculated = calculated;
        }

        /// <summary>
        /// "Copy" constructor
        /// </summary>
        /// <param name="otherAltitude"></param>
        public Altitude(Altitude otherAltitude) : this(otherAltitude.ValueFt, otherAltitude.Mode, otherAltitude.Modifier, otherAltitude.IsCalculated)
        {
            if (otherAltitude == null) throw new ArgumentNullException("otherAltitude");
            this.BoundaryFt = otherAltitude.BoundaryFt;
            this.RawString = otherAltitude.RawString;
        }

        /// <summary>
        /// Value
        /// </summary>
        public double ValueFt
        {
            get { return this._valueFt; }
            set
            {
                this._valueFt = value;
                this._valueM = value.FeetToMeters();
            }
        }

        /// <summary>
        /// Feet with unit
        /// </summary>
        public string ValueFtUnit { get { return this.ValueFt.UnitFt(); } }

        /// <summary>
        /// Meters with unit
        /// </summary>
        public string ValueMUnit { get { return this.ValueM.UnitM(); } }

        /// <summary>
        /// Bounds
        /// </summary>
        public double BoundaryFt
        {
            get { return this._boundaryFt; }
            set
            {
                this._boundaryFt = value;
                this._boundaryFt = 0.3048*value;
            }
        }

        /// <summary>
        /// Value
        /// </summary>
        public double ValueM
        {
            get { return this._valueM; }
            set
            {
                this._valueM = value;
                this._valueFt = value.MetersToFeet();
            }
        }

        /// <summary>
        /// Value
        /// </summary>
        public double BoundaryM
        {
            get { return this._boundaryM; }
            set
            {
                this._boundaryM = value;
                this._boundaryFt = 1/0.3048*value;
            }
        }

        /// <summary>
        /// Bounds
        /// </summary>
        public string BoundaryFtUnit { get { return this._boundaryFt.UnitFt(); } }

        /// <summary>
        /// Bounds
        /// </summary>
        public string BoundaryMUnit { get { return this._boundaryM.UnitM(); } }

        /// <summary>
        /// Mode
        /// </summary>
        public AltitudeMode Mode { get; set; }

        /// <summary>
        /// Modifier
        /// </summary>
        public AltitudeModifier Modifier { get; set; }

        /// <summary>
        /// Raw format
        /// </summary>
        public string RawString { get; set; }

        /// <summary>
        /// Meaningful representation
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder(this.ValueFtUnit);
            sb.Append(' ');
            sb.Append(this.Mode.ToString());
            if (this.Modifier == AltitudeModifier.Range) {
                sb.Append(" Range: ");
                sb.Append(this.BoundaryFtUnit);
            }
            sb.Append(' ');
            sb.Append(this.Modifier.ToString());
            if (this.IsCalculated) {
                sb.Append(" calculated");
            }
            return sb.ToString();
        }

        /// <summary>
        /// Clone
        /// </summary>
        /// <returns></returns>
        public object Clone()
        {
            return new Altitude(this);
        }

        /// <summary>
        /// KML altitude mode
        /// </summary>
        /// <returns></returns>
        public string ModeToKml()
        {
            if (this.Mode == AltitudeMode.Agl) return "<altitudeMode>RelativeToGround</altitudeMode>";
            if (this.Mode == AltitudeMode.Msl) return "<altitudeMode>Absolute</altitudeMode>";
            throw new InvalidOperationException("Unknown mode");
        }

        /// <summary>
        /// Object based on Navigraph PMDG format.
        /// </summary>
        /// <param name="altitudeLine"></param>
        /// <returns></returns>
        public static Altitude AltitudeFromNavigraphPmdgFormat(string altitudeLine)
        {
            if (String.IsNullOrEmpty(altitudeLine)) return null;
            Altitude altitude = null;
            try {
                // AT OR ABOVE 4800
                string alt = altitudeLine.Trim().ToUpperInvariant();
                // the " " is crucial, since there are waypoints such as EDNY / EVTAT
                int ai = altitudeLine.IndexOf(" AT", StringComparison.CurrentCultureIgnoreCase);
                int altFt;
                string[] altParts;
                if (ai >= 0) {
                    alt = alt.Substring(ai).Trim();
                    altParts = alt.Split(' ');
                    if (alt.StartsWith("AT OR ABOVE ")) {
                        altFt = int.Parse(altParts[3]);
                        altitude = new Altitude(altFt, AltitudeMode.Msl, AltitudeModifier.AtOrAbove);
                    }
                    else if (alt.StartsWith("AT OR BELOW ")) {
                        altFt = int.Parse(altParts[3]);
                        altitude = new Altitude(altFt, AltitudeMode.Msl, AltitudeModifier.AtOrBelow);
                    }
                    else if (alt.StartsWith("AT ")) {
                        altFt = int.Parse(altParts[1]);
                        altitude = new Altitude(altFt, AltitudeMode.Msl, AltitudeModifier.Exact);
                    }
                }
                else {
                    if (alt.IndexOf(' ') < 0) return null;
                    altParts = alt.Split(' ');
                    if (altParts.Length > 1 && int.TryParse(altParts[1], out altFt)) {
                        // single number
                        altitude = new Altitude(altFt, AltitudeMode.Msl, AltitudeModifier.Exact);
                    }
                }
            }
            catch (Exception e) {
                throw new ApplicationException("Error parsing altitude: " + altitudeLine, e);
            }
            return altitude;
        }

        /// <summary>
        /// Synopsis
        /// </summary>
        /// <returns></returns>
        public string Synopsis()
        {
            // http://en.wikipedia.org/wiki/Approximation
            StringBuilder sb = new StringBuilder(this.IsCalculated ? "\u2248" : "");
            sb.Append(this.ValueFtUnit);
            sb.Append(' ');
            sb.Append(this.ModeToResourceString());
            if (this.Modifier == AltitudeModifier.Range) {
                sb.Append(' ');
                sb.Append(this.BoundaryFtUnit);
            }
            sb.AppendIfNotNullOrEmpty(this.ModifierToResourceString(), " ");
            return sb.ToString();
        }

        /// <summary>
        /// Mode to resource string
        /// </summary>
        /// <returns></returns>
        private string ModeToResourceString()
        {
            switch (this.Mode) {
                case AltitudeMode.Agl:
                    return Properties.Resources.AltitudeAGL;
                case AltitudeMode.Msl:
                    return Properties.Resources.AltitudeMSL;
            }
            throw new InvalidOperationException("Wrong mode");
        }

        /// <summary>
        /// Mode to resource string
        /// </summary>
        /// <returns></returns>
        private string ModifierToResourceString()
        {
            switch (this.Modifier) {
                case AltitudeModifier.AtOrAbove:
                    return Properties.Resources.AltitudeAtOrAbove;
                case AltitudeModifier.AtOrBelow:
                    return Properties.Resources.AltitudeAtOrBelow;
                case AltitudeModifier.Exact:
                    return "";
                case AltitudeModifier.Range:
                    return Properties.Resources.AltitudeRange;
            }
            throw new InvalidOperationException("Wrong modifier");
        }
    }
}